import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.XMLReader;

import java.sql.Timestamp;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;
import java.io.File;
import java.sql.Time;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import org.apache.commons.lang.StringEscapeUtils;

//change body class
class ChangeBody
{
	//elements
	private String project = null;
	private String commitHash = null;
	private String treeHash = null;
	private String parentHashes = null;
	private String authorName = null;
	private String authorEmail = null;
	private String authorDate = null;
	private String committerName = null;
	private String committerEmail = null;
	private String committerDate = null;
	private String subject = null;
	
	//method to set elements
	public void setElement(int id, String content)
	{
		switch(id)
		{
			case ChangeReader.PROJECT:
			{
				project = content;
				
				break;
			}
			case ChangeReader.COMMITHASH:
			{
				commitHash = content;
				
				break;
			}
			case ChangeReader.TREEHASH:
			{
				treeHash = content;
				
				break;
			}
			case ChangeReader.PARENTHASHES:
			{
				parentHashes = content;
				
				break;
			}
			case ChangeReader.AUTHORNAME:
			{
				authorName = content;
				
				break;
			}
			case ChangeReader.AUTHOREMAIL:
			{
				authorEmail = content;
				
				break;
			}
			case ChangeReader.AUTHORDATE:
			{
				authorDate = content;
				
				break;
			}
			case ChangeReader.COMMITTERNAME:
			{
				committerName = content;
				
				break;
			}
			case ChangeReader.COMMITTEREMAIL:
			{
				committerEmail = content;
				
				break;
			}
			case ChangeReader.COMMITTERDATE:
			{
				committerDate = content;
				
				break;
			}
			case ChangeReader.SUBJECT:
			{
				subject = content;
				
				break;
			}
			default:
			{
				break;
			}
		}
	}
	
	//method to get elements
	public String getElement(int id)
	{
		switch(id)
		{
			case ChangeReader.PROJECT:
			{
				return project;
				
			}
			case ChangeReader.COMMITHASH:
			{
				return commitHash;
			}
			case ChangeReader.TREEHASH:
			{
				return treeHash;
			}
			case ChangeReader.PARENTHASHES:
			{
				return parentHashes;
			}
			case ChangeReader.AUTHORNAME:
			{
				return authorName;
			}
			case ChangeReader.AUTHOREMAIL:
			{
				return authorEmail;
			}
			case ChangeReader.AUTHORDATE:
			{
				return authorDate;
			}
			case ChangeReader.COMMITTERNAME:
			{
				return committerName;
			}
			case ChangeReader.COMMITTEREMAIL:
			{
				return committerEmail;
			}
			case ChangeReader.COMMITTERDATE:
			{
				return committerDate;
			}
			case ChangeReader.SUBJECT:
			{
				return subject;
			}
			default:
			{
				break;
			}
		}
		
		return null;
	}
	
	public String toString()
	{
		String str = new String();
		
		str += ("proj: " + project + "\n");
		str += ("commit hash: " + commitHash + "\n");
		str += ("tree hash: " + treeHash + "\n");
		str += ("parent hash: " + parentHashes + "\n");
		str += ("author name: " + authorName + "\n");
		str += ("author email: " + authorEmail + "\n");
		str += ("author date: " + authorDate + "\n");
		str += ("committer name: " + committerName + "\n");
		str += ("committer email: " + committerEmail + "\n");
		str += ("committer date: " + committerDate + "\n");
		str += ("subject: " + subject + "\n");
		
		
		return str;
	}
}


//a list for change body
class ChangeBodyList
{
	PreparedStatement cbps = null;
	
	private int count = 0;
	
	public ChangeBodyList(PreparedStatement cbps)
	{
		this.cbps = cbps;
	}
	
	private Timestamp toTimestamp(String timeStream)
	{
		if (timeStream.length() == 29 || timeStream.length() == 30)
		{
			//for annoying length difference caused by '4' and '11'
			int offset = 0;
			if (timeStream.length() == 29)
			{
				offset = -1;
			}
			
			//the day
			String day = new String(timeStream.substring(8, 10 + offset));
			
			//convert into numerical month
			int month = -1;
			char firstLetter = Character.toUpperCase(timeStream.charAt(4));
			switch(firstLetter)
			{
				case 'J':
				{
					if (Character.toUpperCase(timeStream.charAt(5)) == 'A')
					{
						month = 0;
					}
						else if (Character.toUpperCase(timeStream.charAt(6)) == 'N')
						{
							month = 5;
						}
						else
						{
							month = 6;
						}
					
					break;
				}
				case 'F':
				{
					month = 1;
					
					break;
				}
				case 'M':
				{
					if (Character.toUpperCase(timeStream.charAt(6)) == 'R')
					{
						month = 2;
					}
					else
					{
						month = 4;
					}
					
					break;
				}
				case 'A':
				{
					if (Character.toUpperCase(timeStream.charAt(5)) == 'P')
					{
						month = 3;
					}
					else
					{
						month = 7;
					}
					
					break;
				}
				case 'S':
				{
					month = 8;
					
					break;
				}
				case 'O':
				{
					month = 9;
					
					break;
				}
				case 'N':
				{
					month = 10;
					
					break;
				}
				case 'D':
				{
					month = 11;
					
					break;
				}
				default:
				{
					break;
				}
			}
			
			//year
			String year = new String(timeStream.substring(20 + offset, 24 + offset));
			
			//hour
			String hour = new String(timeStream.substring(11 + offset, 13 + offset));
			
			//minute
			String min = new String(timeStream.substring(14 + offset, 16 + offset));
			
			//second
			String sec = new String(timeStream.substring(17 + offset, 19 + offset));
		
			//sign
			int sign = timeStream.charAt(25 + offset) == '+' ? 1 : -1;
			
			//offset
			String hourOffset = new String(timeStream.substring(26 + offset, 
					28 + offset));
			
			String minOffset = new String(timeStream.substring(28 + offset, 
					30 + offset));
	
			
			int timeOffset = sign * (Integer.parseInt(hourOffset) * 3600000 
					+ Integer.parseInt(minOffset) * 60000);
		
			
			//set up a timezone
			SimpleTimeZone tZone = new SimpleTimeZone(timeOffset, "");
			
			Calendar dayTime = Calendar.getInstance();
			
			dayTime.setTimeZone(tZone);
			
			//set up the time
			dayTime.set(Integer.parseInt(year), month, Integer.parseInt(day),
					Integer.parseInt(hour), Integer.parseInt(min), Integer.parseInt(sec));
	
			/*
			System.out.println("stamp--> " + timeStream);
			System.out.println("hour offset --> " + hourOffset);
			System.out.println("min offset --> " + minOffset);
			System.out.println(dayTime.getTime().toString());
			*/
	
			return new Timestamp(dayTime.getTime().getTime());
			
			/*
			Timestamp tt = new Timestamp(dayTime.get(Calendar.YEAR) - 1900,
					dayTime.get(Calendar.MONTH), dayTime.get(Calendar.DAY_OF_MONTH),
					dayTime.get(Calendar.HOUR), dayTime.get(Calendar.MINUTE),
					dayTime.get(Calendar.SECOND), 0);
			//System.println();
			
			return tt;
			*/
		}
		
		System.err.print("time format--> " + timeStream);
		
		return null;
	}
	
	//add change body into this list
	//will push into db when necessary
	public void add(ChangeBody cb) throws NumberFormatException, SQLException
	{	
		count++;
		
		//add project
		String project = cb.getElement(ChangeReader.PROJECT);
		if (project == null)
		{
			cbps.setNull(1, Types.VARCHAR);
		}
		else
		{
			cbps.setString(1, project);
		}
		
		//add commit_hash
		String commitHash = cb.getElement(ChangeReader.COMMITHASH);
		if (commitHash == null)
		{
			cbps.setNull(2, Types.VARCHAR);
		}
		else
		{
			cbps.setString(2, commitHash);
		}
		
		//add tree_hash
		String treeHash = cb.getElement(ChangeReader.TREEHASH);
		if (treeHash == null)
		{
			cbps.setNull(3, Types.VARCHAR);
		}
		else
		{

			cbps.setString(3, treeHash);

		}
		
		//add parent_hashes
		String parentHashes = cb.getElement(ChangeReader.PARENTHASHES);
		if (parentHashes == null)
		{
			cbps.setNull(4, Types.VARCHAR);
		}
		else
		{
				cbps.setString(4, parentHashes);
				
				/*
				if (parentHashes.length() > 1000)
				{
					System.err.println("id" + cb.getElement(ChangeReader.TREEHASH));
					System.err.println("parenthash" + parentHashes);
				}
				*/
				
		}
		
		//add author_name
		String authorName= cb.getElement(ChangeReader.AUTHORNAME);
		if (authorName == null)
		{
			cbps.setNull(5, Types.VARCHAR);
		}
		else
		{
			/*
			if (authorName.length() > 200)
			{
				System.err.println(cb);
			}
			*/
			
			cbps.setString(5, authorName);
		}
		
		//add author_e-mail
		String authorEmail= cb.getElement(ChangeReader.AUTHOREMAIL);
		if (authorEmail == null)
		{
			cbps.setNull(6, Types.VARCHAR);
		}
		else
		{
			cbps.setString(6, authorEmail);
		}

		//add author_date
		String authorDate= cb.getElement(ChangeReader.AUTHORDATE);
		
		if (authorDate == null)
		{
			cbps.setNull(7, Types.TIMESTAMP);
		}
		else
		{
			/*
			String dd = toTimestamp(authorDate).toString();
			if (dd.substring(0, 4).equals("1969"))
			{
				System.out.println(cb);
			}
			*/
			
			//cbps.setString(7, toTimestamp(authorDate));
			cbps.setTimestamp(7, toTimestamp(authorDate));
		}
		
		//add commiter_name
		String committerName = cb.getElement(ChangeReader.COMMITTERNAME);
		if (committerName == null)
		{
			cbps.setNull(8, Types.VARCHAR);
		}
		else
		{
			cbps.setString(8, committerName);
		}
		
		//add commiter_email
		String committerEmail = cb.getElement(ChangeReader.COMMITTEREMAIL);
		if (committerEmail == null)
		{
			cbps.setNull(9, Types.VARCHAR);
		}
		else
		{
			cbps.setString(9, committerEmail);
		}
		
		//add committer_date
		String committerDate = cb.getElement(ChangeReader.COMMITTERDATE);
		if (committerDate == null)
		{
			cbps.setNull(10, Types.TIMESTAMP);
		}
		else
		{
			cbps.setTimestamp(10, toTimestamp(committerDate));
			//cbps.setString(10, committerDate);
		}
		
		//add subject
		String subject = cb.getElement(ChangeReader.SUBJECT);
		if (subject == null)
		{
			cbps.setNull(11, Types.CLOB);
		}
		else
		{
			cbps.setString(11, subject);
		}
		
		//add to batch
		cbps.addBatch();
		
		if (count > ChangeReader.CHANGEBODY_BATCH_SIZE)
		{
			count = 0;
			
			outsource();
		}
	}
	
	//push into db
	public void outsource() throws SQLException
	{
		cbps.executeBatch();
	}
	
}

//message class and target class
class MultipleLines
{
	//elements
	private String commitHash;
	private String line;
	
	//method to set element
	public void setElement(int id, String element)
	{
		switch(id)
		{
			case ChangeReader.COMMITHASH:
			{
				commitHash = element;
				
				break;
			}
			case ChangeReader.LINE:
			{
				line = element;
				
				break;
			}
			default:
			{
				break;
			}
		}
	}
	
	//method to get element
	public String getElement(int id)
	{
		switch(id)
		{
			case ChangeReader.COMMITHASH:
			{
				return commitHash;
			}
			case ChangeReader.LINE:
			{
				return line;
			}
			default:
			{
				break;
			}
		}
		
		return null;
	}
}

//message class and target list
class MultipleLinesList
{
	PreparedStatement mlps = null;
	
	private int count = 0;
	
	public MultipleLinesList(PreparedStatement mlps)
	{
		this.mlps = mlps;
	}
	
	//add object to the batch
	public void add(MultipleLines ml) throws SQLException
	{	
		count++;
		
		//add commit hash
		String commitHash = ml.getElement(ChangeReader.COMMITHASH);
		if (commitHash == null)
		{
			mlps.setNull(1, Types.VARCHAR);
		}
		else
		{
			mlps.setString(1, commitHash);
		}
		
		//add line
		String line = ml.getElement(ChangeReader.LINE);
		if (line == null)
		{
			mlps.setNull(2, Types.CLOB);
		}
		else
		{
			mlps.setString(2, line);
		}
		
		//add to batch
		mlps.addBatch();
		
		//System.out.println("target");
		//System.out.println(line);
		
		if (count > ChangeReader.MULTIPLELINES_BATCH_SIZE)
		{
			count = 0;
			
			outsource();
		}
	}
	
	//push into db
	public void outsource() throws SQLException
	{
		mlps.executeBatch();
	}
	
	
}


public class ChangeReader
{
	//for xml filepath
	String filename;
	
	//element labels
	final public static int CHANGE = 0;
	
	//xml reader, used to read in the xml file
	private XMLReader xr = null;
	
	final public static int PROJECT = 1;
	final public static int COMMITHASH = 2;
	final public static int TREEHASH = 3;
	final public static int PARENTHASHES = 4;
	final public static int AUTHORNAME = 5;
	final public static int AUTHOREMAIL = 6;
	final public static int AUTHORDATE = 7;
	final public static int COMMITTERNAME = 8;
	final public static int COMMITTEREMAIL = 9;
	final public static int COMMITTERDATE = 10;
	final public static int SUBJECT = 11;
	
	final public static int LINE = 12;
	
	final public static int MESSAGE = 13;
	
	final public static int TARGET = 14;
	
	//database object
	static DB db = null;
	
	//batch size
	final public static int CHANGEBODY_BATCH_SIZE = 50;
	final public static int MULTIPLELINES_BATCH_SIZE = 100;
	
	//prepared statement
	private PreparedStatement cbps = null;
	private PreparedStatement mlps = null;
	
	//list of changebody, message and target
	private ChangeBodyList cbList = null;
	private MultipleLinesList msList = null;
	private MultipleLinesList tgList = null;
	
	//element label list
	public static HashMap<String, Integer> elementLabelList = null;
	static
	{
		elementLabelList = new HashMap<String, Integer>();
		
		elementLabelList.put("change", CHANGE);
		elementLabelList.put("project", PROJECT);
		elementLabelList.put("commit_hash", COMMITHASH);
		elementLabelList.put("tree_hash", TREEHASH);
		elementLabelList.put("parent_hashes", PARENTHASHES);
		elementLabelList.put("author_name", AUTHORNAME);
		elementLabelList.put("author_e-mail", AUTHOREMAIL);
		elementLabelList.put("author_date", AUTHORDATE);
		elementLabelList.put("commiter_name", COMMITTERNAME);
		elementLabelList.put("commiter_email", COMMITTEREMAIL);
		elementLabelList.put("committer_date", COMMITTERDATE);
		elementLabelList.put("subject", SUBJECT);
		elementLabelList.put("message", MESSAGE);
		elementLabelList.put("target", TARGET);
		elementLabelList.put("line", LINE);
	}
	
	//flag for readable characters
	boolean isReadable = false;
	
	boolean isMessage = false;
	boolean isTarget = false;
	
	class ChangeXMLParser extends DefaultHandler
	{
		//to record current commit hash
		private String commitHash = null;
		
		//object for changebody, message and target
		private ChangeBody cb = null;
		private MultipleLines ml = null;
		
		String elementContent = null;
		
		public ChangeXMLParser()
		{
	    	super();
	    	
			//instantiate cb, ml
			cb = new ChangeBody();
			ml = new MultipleLines();
		}
		
	    public void startDocument()
	    {
	
	    }
	    
	    public void endDocument()
	    {
	    	//push remain items in buffer into db
	    	try
	    	{
	    		cbList.outsource();
	    	}
	    	catch(Exception e)
	    	{
	    		e.printStackTrace();
	    	}
	    	
	    	try
	    	{
	    		msList.outsource();
	    	}
	    	catch(Exception e)
	    	{
	    		e.printStackTrace();
	    	}
	    	
	    	try
	    	{
	    		tgList.outsource();
	    	}
	    	catch(Exception e)
	    	{
	    		e.printStackTrace();
	    	}
	    }
	    
	    private String normalize(String str)
	    {
	    	if (str != null && !str.trim().equalsIgnoreCase("null") 
	    			&& !str.trim().equals(""))
	    	{
	    		str = str.trim();
	    		return StringEscapeUtils.escapeHtml(str);
	    		
	    		//return str;
	    	}
	    	
	    	return null;
	    }
	    
	    public void startElement (String uri, String name, 
	    		String qName, Attributes atts)
	    {	
	    	//get current element's label
	    	Integer currentElement = elementLabelList.get(name.trim());
	    	
	    	elementContent = null;
	   
	    	if (currentElement != null 
	    			&& currentElement >= CHANGE && currentElement <= TARGET) 
	    	{
				switch (currentElement) 
				{
					case CHANGE: 
					{
						//cb = new ChangeBody();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
						
						break;
					}
					case MESSAGE: 
					{			
						isMessage = true;
						
						//ml = new MultipleLines();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
											
						break;
					}
					case TARGET: 
					{
						isTarget = true;
						
						//ml = new MultipleLines();
						
						//set it to false, unreadable
						isReadable = false;
						
			    		//elementContent = null;
						
						break;
					}
					default: 
					{
						if ((currentElement >= PROJECT && currentElement <= LINE))
						{
							isReadable = true;
						
							//elementContent = new String();
						}
						
						break;
					}
				}
			}
	    	else
	    	{
	    		//set it to false, unreadable
	    		isReadable = false;
	    		
	    		elementContent = null;
	    	}
	    }
	    
	    public void endElement (String uri, String name, String qName)
	    {
	    	//get current element's label
	    	Integer currentElement = elementLabelList.get(name.trim());

	    	if(currentElement != null 
	    			&& currentElement >= CHANGE && currentElement <= TARGET)
	    	{
	    		
		    	switch(currentElement)
		    	{
		    		case CHANGE:
		    		{
		    			//add this changebody element to its list
		    			try
		    			{
							cbList.add(cb);
		    			}
		    			catch(Exception e)
		    			{
		    				e.printStackTrace();
		    			}
		    			
		    			break;
		    		}
		    		case MESSAGE:
		    		{
		    			isMessage = false;
		 	
		    			break;
		    		}
		    		case TARGET:
		    		{
		    			isTarget = false;
		    			
		    			break;
		    		}
		    		case COMMITHASH:
		    		{
		    			//record current commit hash
		    			commitHash = elementContent;
		    			
	    				//add commit hash to cb
	    				cb.setElement(COMMITHASH, 
	    						elementContent);
		    			
		    			break;
		    		}
		    		case LINE:
		    		{
		    			//add line to it
		    			ml.setElement(COMMITHASH, commitHash);
		    			ml.setElement(LINE, normalize(elementContent));
		    			
		    			//System.out.println(elementContent);
		    			
		    			//add it to the list
		    			if (isMessage)
		    			{	
		    				try
		    				{
		    					msList.add(ml);
		    				}
		    				catch(Exception e)
		    				{
		    					e.printStackTrace();
		    				}
		    			}
		    			else if (isTarget)
		    			{
		    				try
		    				{
		    					tgList.add(ml);
		    				}
		    				catch(Exception e)
		    				{
		    					e.printStackTrace();
		    				}
		    			}
		    			
		    			break;
		    		}
		    		default:
		    		{	
		    			if (isReadable)
		    			{
		    				//normal element, just add it to cb
		    				cb.setElement(elementLabelList.get(name), 
		    						normalize(elementContent));
		    			}
		    			break;
		    		}    	
		    	}
		    	
	    	}
	    	
	    	//reset this flag to make following text unreadable
			isReadable = false;
			
			elementContent= null;
	    }
	    
	    public void characters (char ch[], int start, int length)
	    {
	    	//read in characters when current element is meaningful
	    	if (isReadable == true)
	    	{
	    		//System.out.println(new String(ch, start, length));
	    		
	    		//elementContent += normalize(new String(ch, start, length));
	    		
	    	
	    		if ((elementContent != null) || (length > 0))
	    		{	    		
		    		elementContent = (elementContent == null) 
		    				? normalize(new String(ch, start, length))
		    				: elementContent + normalize(new String(ch, start, length));
	    		}

	    	
	    		//elementContent = normalize(new String(ch, start, length));
	    		//System.out.println(elementContent);
	    	}
	    }
	}
	
	public ChangeReader(String filename) throws Exception
	{
		//pass in the filepath
		this.filename = filename;
		
		//instantiate an XML reader
		xr = XMLReaderFactory.createXMLReader();
		
		//set event handler and error handler
		ChangeXMLParser handler = new ChangeXMLParser();
		xr.setContentHandler(handler);
		xr.setErrorHandler(handler);
		
		//set up db
		db = new DB();
		
		try
		{
			db.connect();

			db.dropTable("changeBody");
			db.buildTable("changeBody");
			
			db.dropTable("message");
			db.buildTable("message");
			
			db.dropTable("target");
			db.buildTable("target");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
    	
		//set up changebody, message, target lists
    	cbList = new ChangeBodyList(db.getPreparedStatement("changeBody"));
    	msList = new MultipleLinesList(db.getPreparedStatement("message"));
    	tgList = new MultipleLinesList(db.getPreparedStatement("target"));
	}
	
	private void parseXML()
	{
		try
		{
			xr.parse(filename);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public static void main(String args[]) throws Exception
	{
		DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
		Date date = new Date();
		System.out.println(dateFormat.format(date));
		
		File dir = new File("data");
		String filepath = dir.getAbsolutePath() + "/git.log.xml";
		//String filepath = dir.getAbsolutePath() + "/view.xml";
		

		ChangeReader cr = new ChangeReader(filepath);

		
		cr.parseXML();
		
		db.close();
		
		date = new Date();
		System.out.println(dateFormat.format(date));

	}
}